Vollständiger Leitfaden von der Geschichte bis zur Implementierung
Theorie • Praxis • Best Practices
DRF Entwicklungsgeschichte
Warum wurde es eingeführt?
Serialisierung verstehen
CRUD-Operationen
URL-Design
Zusatz-Packages
Filter & Pagination
Authentication
Schritt-für-Schritt
Movie-API erstellen
Testen & Debuggen
Tom Christie startet DRF als Open-Source-Projekt
Durchbruch mit revolutionären Features
Mozilla unterstützt DRF finanziell
DRF wird zum De-facto-Standard
Version 3.14+ - Reif & Feature-reich
def movie_list(request):
if request.method == 'GET':
movies = Movie.objects.all()
data = []
for movie in movies:
data.append({
'id': movie.id,
'title': movie.title,
'year': movie.year,
# ... 20 Felder manuell ...
})
return JsonResponse(data, safe=False)
elif request.method == 'POST':
data = json.loads(request.body)
# Validierung manuell
if not data.get('title'):
return JsonResponse({'error': 'Title required'}, status=400)
# ... 50 Zeilen Validierung ...
try:
movie = Movie.objects.create(...)
except Exception as e:
# ... Fehlerbehandlung ...
return JsonResponse({...}, status=201)
# Das gleiche für PUT, PATCH, DELETE... 😫
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
class MovieViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
# Fertig! 🎉
# GET, POST, PUT, PATCH, DELETE funktionieren!
"Make building Web APIs as simple as possible"
- Tom Christie, Creator of DRF
Die Umwandlung von komplexen Datenstrukturen in ein einfaches Format (und zurück)
JSON → Python-Objekt
# Client sendet JSON:
{
"title": "Inception",
"year": 2010,
"rating": "8.8"
}
# ↓ Deserialisierung ↓
# Django erstellt:
movie = Movie(
title="Inception",
year=2010,
rating=Decimal('8.8')
)
movie.save()
Python-Objekt → JSON
# Django hat:
movie = Movie.objects.get(pk=1)
#
# ↓ Serialisierung ↓
# Client erhält JSON:
{
"id": 1,
"title": "Inception",
"year": 2010,
"rating": "8.8",
"created_at": "2024-01-15T10:00:00Z"
}
Der Standard für Web-APIs
{
"title": "Inception",
"year": 2010,
"rating": 8.8
}
Legacy-Standard
<movie>
<title>Inception</title>
<year>2010</year>
<rating>8.8</rating>
</movie>
Binäres Format (kompakt)
# Binär codiert (kleiner als JSON)
# Gleiche Daten wie JSON, aber:
# JSON: 54 bytes
# MessagePack: 33 bytes
Google's Binärformat
// .proto Datei
message Movie {
string title = 1;
int32 year = 2;
float rating = 3;
}
Human-friendly Format
title: Inception
year: 2010
rating: 8.8
MongoDB's Format
// Binär, aber JSON-ähnlich
// Unterstützt mehr Datentypen:
{
"date": ISODate("2024-01-15"),
"binary": BinData(...)
}
rest_framework.renderers.JSONRendererrest_framework.renderers.BrowsableAPIRendererCreate, Retrieve, Update, Delete - Basis jeder Datenbank-Anwendung
HTTP: POST
POST /api/movies/
Content-Type: application/json
{
"title": "Inception",
"year": 2010
}
→ 201 Created
{
"id": 1,
"title": "Inception",
"year": 2010
}
HTTP: GET
# Liste aller Ressourcen
GET /api/movies/
→ 200 OK
[
{"id": 1, "title": "Inception"},
{"id": 2, "title": "Matrix"}
]
# Eine spezifische Ressource
GET /api/movies/1/
→ 200 OK
{"id": 1, "title": "Inception", ...}
HTTP: PUT / PATCH
# PUT: Komplette Aktualisierung
PUT /api/movies/1/
{
"title": "Inception",
"year": 2010,
"rating": 9.0
}
→ 200 OK
# PATCH: Teilweise Aktualisierung
PATCH /api/movies/1/
{
"rating": 9.0
}
→ 200 OK
HTTP: DELETE
DELETE /api/movies/1/
→ 204 No Content
# Ressource ist gelöscht
GET /api/movies/1/
→ 404 Not Found
Alle 5 CRUD-Operationen automatisch: list, create, retrieve, update, partial_update, destroy
URLs sollten intuitiv, konsistent und ressourcen-orientiert sein
# Verben in URLs (falsch!)
GET /api/getAllMovies
GET /api/getMovie?id=1
POST /api/createMovie
POST /api/updateMovie?id=1
POST /api/deleteMovie?id=1
# Probleme:
- Inkonsistent
- Nicht REST-konform
- Schwer zu merken
- Skaliert nicht
# Ressourcen-orientiert
GET /api/movies/ # Liste
POST /api/movies/ # Erstellen
GET /api/movies/1/ # Details
PUT /api/movies/1/ # Aktualisieren
PATCH /api/movies/1/ # Teil-Update
DELETE /api/movies/1/ # Löschen
# Vorteile:
✅ HTTP-Methoden nutzen Semantik
✅ Konsistent
✅ Intuitiv
✅ Standard-konform
/movies nicht /getMovies/movies/ nicht /movie//movies/1/ nicht /movies?id=1/movies/1/castings//movie-genres/ nicht /MovieGenres//api/movies/
/api/artists/
/api/castings/
/api/movies/1/castings/ # Besetzung eines Films
/api/artists/5/movies/ # Filme eines Künstlers
/api/movies/top-rated/ # Custom Endpoint
/api/movies/recent/ # Custom Filter
/api/artists/1/filmography/ # Detailansicht
/api/v1/movies/ # Version 1
/api/v2/movies/ # Version 2
# STANDARD CRUD
GET /api/movies/ # Alle Filme
POST /api/movies/ # Film erstellen
GET /api/movies/{id}/ # Film-Details
PUT /api/movies/{id}/ # Film komplett aktualisieren
PATCH /api/movies/{id}/ # Film teilweise aktualisieren
DELETE /api/movies/{id}/ # Film löschen
# FILTERING & SEARCH
GET /api/movies/?year=2010 # Filter nach Jahr
GET /api/movies/?genre=Sci-Fi # Filter nach Genre
GET /api/movies/?search=Matrix # Suche im Titel
GET /api/movies/?ordering=-rating # Sortierung nach Rating
# PAGINATION
GET /api/movies/?page=2 # Seite 2
GET /api/movies/?page=2&page_size=5 # 5 Einträge pro Seite
# RELATIONSHIPS
GET /api/movies/{id}/castings/ # Besetzung eines Films
GET /api/artists/{id}/movies/ # Filme eines Künstlers
# CUSTOM ACTIONS
GET /api/movies/top-rated/ # Top 10 Filme
GET /api/movies/recent/ # Aktuelle Filme
GET /api/movies/{id}/similar/ # Ähnliche Filme
# BATCH OPERATIONS (erweitert)
POST /api/movies/bulk-create/ # Mehrere Filme erstellen
PATCH /api/movies/bulk-update/ # Mehrere Filme updaten
DELETE /api/movies/bulk-delete/ # Mehrere Filme löschen
Mit DefaultRouter werden Standard-URLs automatisch erstellt!
Fortgeschrittenes Filtering
pip install django-filter
# Ermöglicht:
GET /api/movies/?year__gte=2010
GET /api/movies/?rating__range=8,10
GET /api/movies/?genre__icontains=sci
OpenAPI/Swagger Dokumentation
pip install drf-spectacular
# Generiert automatisch:
- Swagger UI
- OpenAPI 3.0 Schema
- Interaktive API-Doku
JWT Authentication
pip install djangorestframework-simplejwt
# JSON Web Tokens für Auth:
POST /api/token/
{
"username": "user",
"password": "pass"
}
→ {"access": "...", "refresh": "..."}
CORS-Support
pip install django-cors-headers
# Erlaubt API-Zugriff von anderen Domains:
- Frontend auf localhost:3000
- Mobile Apps
- Third-Party Services
Geo-Daten Support
pip install djangorestframework-gis
# Für Location-basierte APIs:
- GPS-Koordinaten
- Geo-Queries (Umkreissuche)
- GeoJSON Format
Excel Export
pip install drf-excel
# Export als Excel:
GET /api/movies/?format=xlsx
→ Excel-Datei Download
Verschachtelte URLs
pip install drf-nested-routers
# Ermöglicht:
/api/movies/1/castings/
/api/movies/1/castings/5/
Alternative API-Doku
pip install drf-yasg
# Swagger & ReDoc UI
# Ältere Alternative zu drf-spectacular
Granulare Permissions
pip install drf-access-policy
# Policy-basierte Zugriffskontrolle
# Besser als Django's Standard-Permissions
Profiling & Monitoring
pip install django-silk
# Performance-Analyse:
- SQL-Query-Tracking
- Request/Response-Profiling
- Bottleneck-Erkennung
FirstMovieAPI/
├── firstmovieapi/ # Projekt-Settings
│ ├── __init__.py
│ ├── settings.py # ← Werden wir anpassen
│ ├── urls.py # ← Werden wir anpassen
│ └── wsgi.py
├── movies/ # Unsere App
│ ├── __init__.py
│ ├── models.py # ← Bereits vorhanden (Movie, Artist, MovieCasting)
│ ├── serializers.py # ← NEU erstellen
│ ├── views.py # ← NEU erstellen
│ ├── urls.py # ← NEU erstellen
│ ├── admin.py
│ └── migrations/
├── manage.py
└── db.sqlite3
class Movie(models.Model):
title = models.CharField(max_length=200)
year = models.IntegerField()
genre = models.CharField(max_length=100, blank=True)
rating = models.DecimalField(max_digits=3, decimal_places=1, null=True, blank=True)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Artist(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birth_date = models.DateField(null=True, blank=True)
nationality = models.CharField(max_length=100, blank=True)
biography = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class MovieCasting(models.Model):
movie = models.ForeignKey(Movie, on_delete=models.CASCADE, related_name='castings')
artist = models.ForeignKey(Artist, on_delete=models.CASCADE, related_name='movie_roles')
role_name = models.CharField(max_length=200)
is_main_role = models.BooleanField(default=False)
order = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
# Terminal / PowerShell:
pip install djangorestframework
# Output:
Collecting djangorestframework
Successfully installed djangorestframework-3.14.0
pip install django-filter
# Output:
Collecting django-filter
Successfully installed django-filter-23.5
pip install django-cors-headers
# Nur nötig wenn Frontend von anderer Domain zugreift
pip install drf-spectacular
# Generiert automatisch Swagger/OpenAPI Dokumentation
# filepath: firstmovieapi/settings.py
# ...existing code...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Third-party apps
'rest_framework', # ← NEU! Django REST Framework
'django_filters', # ← NEU! Filtering-Support
'corsheaders', # ← NEU! (optional) CORS-Support
'drf_spectacular', # ← NEU! (optional) API-Dokumentation
# Local apps
'movies',
]
# ...existing code...
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'corsheaders.middleware.CorsMiddleware', # ← NEU! (optional) CORS
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# ...existing code...
# ==============================================
# REST FRAMEWORK KONFIGURATION (am Ende der Datei)
# ==============================================
REST_FRAMEWORK = {
# Schema-Generator (für Swagger)
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', # optional
# Permissions (Zugriffsrechte)
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny', # Für Development: Jeder hat Zugriff
# In Produktion: 'rest_framework.permissions.IsAuthenticated',
],
# Pagination (Seitennummerierung)
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10, # 10 Einträge pro Seite
# Renderer (Output-Formate)
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer', # JSON-Output
'rest_framework.renderers.BrowsableAPIRenderer', # Interaktive Web-UI
],
# Filter Backends
'DEFAULT_FILTER_BACKENDS': [
'django_filters.rest_framework.DjangoFilterBackend', # django-filter
'rest_framework.filters.SearchFilter', # Suche
'rest_framework.filters.OrderingFilter', # Sortierung
],
# Throttling (Rate Limiting) - optional
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle', # Für anonyme User
'rest_framework.throttling.UserRateThrottle', # Für eingeloggte User
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/hour', # 100 Requests pro Stunde für Anonyme
'user': '1000/hour', # 1000 Requests pro Stunde für User
},
}
# ==============================================
# CORS HEADERS KONFIGURATION (optional)
# ==============================================
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000", # React/Vue/Angular Frontend
"http://localhost:8080",
"http://127.0.0.1:3000",
]
# Oder für Development: ALLE Domains erlauben (NICHT in Produktion!)
# CORS_ALLOW_ALL_ORIGINS = True
# ==============================================
# DRF SPECTACULAR KONFIGURATION (optional)
# ==============================================
SPECTACULAR_SETTINGS = {
'TITLE': 'Movie API',
'DESCRIPTION': 'REST API für Filme, Künstler und Besetzungen',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
}
Konvertieren Models automatisch zu JSON (und zurück)
# filepath: movies/serializers.py
from rest_framework import serializers
from .models import Movie, Artist, MovieCasting
class MovieSerializer(serializers.ModelSerializer):
"""
Serializer für Movie Model
Konvertiert Movie-Objekte zu JSON und validiert Input
"""
# Berechnetes Feld: Alter des Films
age = serializers.SerializerMethodField()
class Meta:
model = Movie
fields = '__all__' # Alle Felder einbeziehen
read_only_fields = ['created_at', 'updated_at'] # Nicht änderbar
def get_age(self, obj):
"""Berechne das Alter des Films"""
from datetime import datetime
return datetime.now().year - obj.year
def validate_year(self, value):
"""Validiere das Jahr"""
from datetime import datetime
current_year = datetime.now().year
if value < 1888: # Erstes Kino-Jahr
raise serializers.ValidationError(
"Das Jahr kann nicht vor 1888 liegen (Geburt des Kinos)"
)
if value > current_year + 5: # Max 5 Jahre in Zukunft
raise serializers.ValidationError(
f"Das Jahr kann nicht mehr als 5 Jahre in der Zukunft liegen"
)
return value
def validate_rating(self, value):
"""Validiere die Bewertung"""
if value is not None and (value < 0 or value > 10):
raise serializers.ValidationError(
"Die Bewertung muss zwischen 0 und 10 liegen"
)
return value
class ArtistSerializer(serializers.ModelSerializer):
"""
Serializer für Artist Model
"""
# Property aus Model als Read-Only Feld
full_name = serializers.ReadOnlyField()
# Berechnetes Feld: Anzahl Filme
movie_count = serializers.SerializerMethodField()
class Meta:
model = Artist
fields = '__all__'
read_only_fields = ['created_at', 'updated_at']
def get_movie_count(self, obj):
"""Anzahl der Filme, in denen der Künstler mitspielt"""
return obj.movie_roles.count()
class MovieCastingSerializer(serializers.ModelSerializer):
"""
Einfacher Casting Serializer (nur IDs)
Für CREATE/UPDATE Operationen
"""
class Meta:
model = MovieCasting
fields = '__all__'
read_only_fields = ['created_at']
class MovieCastingDetailSerializer(serializers.ModelSerializer):
"""
Detaillierter Casting Serializer mit verschachtelten Objekten
Für LIST/RETRIEVE Operationen
"""
# Verschachtelte Serializer (für Output)
movie = MovieSerializer(read_only=True)
artist = ArtistSerializer(read_only=True)
# IDs für Input (Write-Only)
movie_id = serializers.PrimaryKeyRelatedField(
queryset=Movie.objects.all(),
source='movie',
write_only=True
)
artist_id = serializers.PrimaryKeyRelatedField(
queryset=Artist.objects.all(),
source='artist',
write_only=True
)
class Meta:
model = MovieCasting
fields = '__all__'
read_only_fields = ['created_at']
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
fields = '__all__' - Alle Felderfields = ['id', 'title'] - Nur bestimmteexclude = ['password'] - Bestimmte ausschließenread_only_fields = ['created_at', 'updated_at']
password = serializers.CharField(
write_only=True
)
age = serializers.SerializerMethodField()
def get_age(self, obj):
return datetime.now().year - obj.year
get_<feldname>def validate_year(self, value):
if value < 1888:
raise serializers.ValidationError(
"Jahr zu früh"
)
return value
validate_<feldname>ValidationErrormovie = MovieSerializer(read_only=True)
artist = ArtistSerializer(read_only=True)
read_only=True empfohlenAutomatische Implementierung von list, create, retrieve, update, destroy
# filepath: movies/views.py
from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from .models import Movie, Artist, MovieCasting
from .serializers import (
MovieSerializer,
ArtistSerializer,
MovieCastingSerializer,
MovieCastingDetailSerializer
)
class MovieViewSet(viewsets.ModelViewSet):
"""
ViewSet für Movie Model
Bietet automatisch folgende Endpoints:
- list (GET /api/movies/) - Alle Filme
- create (POST /api/movies/) - Film erstellen
- retrieve (GET /api/movies/{id}/) - Film-Details
- update (PUT /api/movies/{id}/) - Film komplett aktualisieren
- partial_update (PATCH /api/movies/{id}/) - Film teilweise aktualisieren
- destroy (DELETE /api/movies/{id}/) - Film löschen
"""
queryset = Movie.objects.all()
serializer_class = MovieSerializer
# Filter Backends
filter_backends = [
DjangoFilterBackend, # Für exakte Filter
filters.SearchFilter, # Für Textsuche
filters.OrderingFilter # Für Sortierung
]
# Welche Felder können gefiltert werden?
filterset_fields = ['year', 'genre'] # ?year=2010&genre=Sci-Fi
# Welche Felder können durchsucht werden?
search_fields = ['title', 'description'] # ?search=Matrix
# Welche Felder können sortiert werden?
ordering_fields = ['year', 'rating', 'title'] # ?ordering=-rating
# Standard-Sortierung
ordering = ['-year', 'title'] # Neueste zuerst, dann alphabetisch
@action(detail=True, methods=['get'])
def castings(self, request, pk=None):
"""
Custom Endpoint: GET /api/movies/{id}/castings/
Gibt alle Besetzungen eines Films zurück
"""
movie = self.get_object()
castings = movie.castings.all()
serializer = MovieCastingDetailSerializer(castings, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def top_rated(self, request):
"""
Custom Endpoint: GET /api/movies/top_rated/
Gibt die Top 10 bestbewerteten Filme zurück
"""
movies = Movie.objects.filter(
rating__isnull=False
).order_by('-rating')[:10]
serializer = self.get_serializer(movies, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def recent(self, request):
"""
Custom Endpoint: GET /api/movies/recent/
Gibt Filme der letzten 5 Jahre zurück
"""
from datetime import datetime
current_year = datetime.now().year
movies = Movie.objects.filter(year__gte=current_year - 5)
serializer = self.get_serializer(movies, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def add_to_favorites(self, request, pk=None):
"""
Custom Endpoint: POST /api/movies/{id}/add_to_favorites/
Beispiel für Custom POST Action
"""
movie = self.get_object()
# Hier könnte Logik für Favoriten sein
return Response({
'status': 'Film zu Favoriten hinzugefügt',
'movie': movie.title
})
class ArtistViewSet(viewsets.ModelViewSet):
"""
ViewSet für Artist Model
"""
queryset = Artist.objects.all()
serializer_class = ArtistSerializer
filter_backends = [
filters.SearchFilter,
filters.OrderingFilter
]
search_fields = ['first_name', 'last_name', 'nationality', 'biography']
ordering_fields = ['last_name', 'first_name', 'birth_date']
ordering = ['last_name', 'first_name']
@action(detail=True, methods=['get'])
def movies(self, request, pk=None):
"""
Custom Endpoint: GET /api/artists/{id}/movies/
Gibt alle Filme eines Künstlers zurück
"""
artist = self.get_object()
castings = artist.movie_roles.all()
movies = [casting.movie for casting in castings]
serializer = MovieSerializer(movies, many=True)
return Response(serializer.data)
@action(detail=True, methods=['get'])
def filmography(self, request, pk=None):
"""
Custom Endpoint: GET /api/artists/{id}/filmography/
Detaillierte Filmografie mit Rollen
"""
artist = self.get_object()
castings = artist.movie_roles.all().select_related('movie')
# Gruppiere nach Hauptrolle/Nebenrolle
main_roles = castings.filter(is_main_role=True)
supporting_roles = castings.filter(is_main_role=False)
return Response({
'artist': ArtistSerializer(artist).data,
'main_roles': MovieCastingDetailSerializer(main_roles, many=True).data,
'supporting_roles': MovieCastingDetailSerializer(supporting_roles, many=True).data,
'total_movies': castings.count()
})
class MovieCastingViewSet(viewsets.ModelViewSet):
"""
ViewSet für MovieCasting Model
"""
queryset = MovieCasting.objects.all().select_related('movie', 'artist')
filter_backends = [DjangoFilterBackend]
filterset_fields = ['movie', 'artist', 'is_main_role']
def get_serializer_class(self):
"""
Wähle den passenden Serializer basierend auf der Action
"""
if self.action in ['list', 'retrieve']:
# Für Anzeige: Mit verschachtelten Objekten
return MovieCastingDetailSerializer
# Für Create/Update: Nur IDs
return MovieCastingSerializer
class MovieViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
Generiert automatisch:
list() → GET /api/movies/create() → POST /api/movies/retrieve() → GET /api/movies/{id}/update() → PUT /api/movies/{id}/partial_update() → PATCH /api/movies/{id}/destroy() → DELETE /api/movies/{id}/@action(detail=True, methods=['get'])
def castings(self, request, pk=None):
movie = self.get_object()
# ...
return Response(data)
detail=True → Braucht ID/api/movies/{id}/castings/pk Parameter automatischself.get_object() holt das Objekt@action(detail=False, methods=['get'])
def top_rated(self, request):
movies = Movie.objects.filter(...)
# ...
return Response(data)
detail=False → Keine ID nötig/api/movies/top_rated/@action(detail=True, methods=['post'])
def add_to_favorites(self, request, pk=None):
movie = self.get_object()
# Verarbeite request.data
return Response({'status': 'ok'})
methods=['post'] → POST-Requestrequest.dataDRF Router erstellt alle URLs aus ViewSets
# filepath: movies/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import MovieViewSet, ArtistViewSet, MovieCastingViewSet
# Router erstellen
router = DefaultRouter()
# ViewSets beim Router registrieren
router.register(r'movies', MovieViewSet, basename='movie')
router.register(r'artists', ArtistViewSet, basename='artist')
router.register(r'castings', MovieCastingViewSet, basename='casting')
# URLs
urlpatterns = [
# Router-URLs einbinden
path('', include(router.urls)),
]
# filepath: firstmovieapi/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
# Django Admin
path('admin/', admin.site.urls),
# API Endpunkte
path('api/', include('movies.urls')),
# DRF Login/Logout für Browsable API
path('api-auth/', include('rest_framework.urls')),
# Optional: Swagger Dokumentation
# path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
# path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
]
# MOVIES
GET /api/movies/ # Liste aller Filme
POST /api/movies/ # Film erstellen
GET /api/movies/{id}/ # Film-Details
PUT /api/movies/{id}/ # Film komplett aktualisieren
PATCH /api/movies/{id}/ # Film teilweise aktualisieren
DELETE /api/movies/{id}/ # Film löschen
GET /api/movies/{id}/castings/ # Custom: Besetzung
GET /api/movies/top_rated/ # Custom: Top-Filme
GET /api/movies/recent/ # Custom: Aktuelle Filme
# ARTISTS
GET /api/artists/ # Liste aller Künstler
POST /api/artists/ # Künstler erstellen
GET /api/artists/{id}/ # Künstler-Details
PUT /api/artists/{id}/ # Künstler aktualisieren
PATCH /api/artists/{id}/ # Künstler teilweise aktualisieren
DELETE /api/artists/{id}/ # Künstler löschen
GET /api/artists/{id}/movies/ # Custom: Filme des Künstlers
GET /api/artists/{id}/filmography/ # Custom: Filmografie
# CASTINGS
GET /api/castings/ # Liste aller Besetzungen
POST /api/castings/ # Besetzung erstellen
GET /api/castings/{id}/ # Besetzung-Details
PUT /api/castings/{id}/ # Besetzung aktualisieren
PATCH /api/castings/{id}/ # Besetzung teilweise aktualisieren
DELETE /api/castings/{id}/ # Besetzung löschen
# In ViewSet definiert:
filterset_fields = ['year', 'genre']
# Verwendung:
GET /api/movies/?year=2010
GET /api/movies/?genre=Sci-Fi
GET /api/movies/?year=2010&genre=Action
# Django ORM Lookups:
GET /api/movies/?year__gte=2010 # Jahr >= 2010
GET /api/movies/?year__lte=2020 # Jahr <= 2020
GET /api/movies/?year__range=2010,2020 # 2010-2020
GET /api/movies/?rating__gte=8.0 # Rating >= 8.0
GET /api/movies/?genre__icontains=sci # Genre enthält "sci"
Verfügbare Lookups:
__exact - Genau gleich__iexact - Case-insensitive gleich__contains - Enthält__icontains - Case-insensitive enthält__gt, __gte - Greater than (or equal)__lt, __lte - Less than (or equal)__range - Zwischen zwei Werten__isnull - Ist NULL# In ViewSet definiert:
search_fields = ['title', 'description']
# Verwendung:
GET /api/movies/?search=Matrix
GET /api/movies/?search=Inception
# Sucht in title UND description
# Case-insensitive
# Partial Match (enthält)
# In ViewSet definiert:
ordering_fields = ['year', 'rating', 'title']
ordering = ['-year'] # Standard
# Verwendung:
GET /api/movies/?ordering=year # Aufsteigend
GET /api/movies/?ordering=-year # Absteigend
GET /api/movies/?ordering=-rating,title # Mehrfach
# filepath: movies/filters.py
import django_filters
from .models import Movie
class MovieFilter(django_filters.FilterSet):
"""Custom Filter für Movie Model"""
# Jahr-Range
year_min = django_filters.NumberFilter(field_name='year', lookup_expr='gte')
year_max = django_filters.NumberFilter(field_name='year', lookup_expr='lte')
# Rating-Range
rating_min = django_filters.NumberFilter(field_name='rating', lookup_expr='gte')
rating_max = django_filters.NumberFilter(field_name='rating', lookup_expr='lte')
# Genre mit Case-Insensitive
genre = django_filters.CharFilter(lookup_expr='icontains')
# Titel enthält
title_contains = django_filters.CharFilter(field_name='title', lookup_expr='icontains')
# Nur Filme mit Rating
has_rating = django_filters.BooleanFilter(field_name='rating', lookup_expr='isnull', exclude=True)
class Meta:
model = Movie
fields = ['genre']
# filepath: movies/views.py
from .filters import MovieFilter
class MovieViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
filterset_class = MovieFilter # ← Custom FilterSet
# ...existing code...
GET /api/movies/?year_min=2010&year_max=2020
GET /api/movies/?rating_min=8.0
GET /api/movies/?title_contains=matrix
GET /api/movies/?has_rating=true
GET /api/movies/?genre=sci&year_min=2010&rating_min=8.0
DRF paginiert automatisch große Datenmengen
GET /api/movies/?page=2
{
"count": 145,
"next": "http://api.example.org/movies/?page=3",
"previous": "http://api.example.org/movies/?page=1",
"results": [
{"id": 11, "title": "Movie 11", ...},
{"id": 12, "title": "Movie 12", ...},
// ... 10 Einträge pro Seite
]
}
# In settings.py:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS':
'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
# Verwendung:
GET /api/movies/?page=1
GET /api/movies/?page=2
GET /api/movies/?page=3
# Flexiblere Kontrolle:
GET /api/movies/?limit=20&offset=0 # Erste 20
GET /api/movies/?limit=20&offset=20 # Nächste 20
# Für sehr große Datasets (Performance):
GET /api/movies/?cursor=cD0yMDIz...
# Client kann Page Size ändern:
GET /api/movies/?page=1&page_size=50
# Max Limit setzen in settings.py:
REST_FRAMEWORK = {
'PAGE_SIZE': 10,
'MAX_PAGE_SIZE': 100, # Maximal 100
}
# Terminal:
python manage.py makemigrations
python manage.py migrate
# Output:
Migrations for 'movies':
movies/migrations/0001_initial.py
- Create model Movie
- Create model Artist
- Create model MovieCasting
Operations to perform:
Apply all migrations: movies
Running migrations:
Applying movies.0001_initial... OK
# Terminal:
python manage.py createsuperuser
# Eingaben:
Username: admin
Email: admin@example.com
Password: ********
Password (again): ********
Superuser created successfully.
# Terminal:
python manage.py runserver
# Output:
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Teste deine API direkt im Browser - keine extra Tools nötig
http://127.0.0.1:8000/api/
# Du siehst:
- movies: http://127.0.0.1:8000/api/movies/
- artists: http://127.0.0.1:8000/api/artists/
- castings: http://127.0.0.1:8000/api/castings/
Die Browsable API zeigt:
Gehe zu: http://127.0.0.1:8000/api/movies/
Scrolle nach unten zum Formular:
{
"title": "The Matrix",
"year": 1999,
"genre": "Sci-Fi",
"rating": "8.7",
"description": "A computer hacker learns from mysterious rebels about the true nature of his reality."
}
Klicke auf "POST"
✅ Film wurde erstellt mit ID 1
Gleiche Seite neu laden → Siehst die Filmliste
[
{
"id": 1,
"title": "The Matrix",
"year": 1999,
"genre": "Sci-Fi",
"rating": "8.7",
"description": "A computer hacker...",
"age": 25,
"created_at": "2024-11-16T10:00:00Z",
"updated_at": "2024-11-16T10:00:00Z"
}
]
Gehe zu: http://127.0.0.1:8000/api/movies/1/
Wähle "PATCH" aus dem Dropdown
{
"rating": "9.0"
}
Klicke auf "PATCH"
✅ Rating wurde aktualisiert
# PowerShell:
$body = @{
title = "Inception"
year = 2010
genre = "Sci-Fi"
rating = "8.8"
description = "A thief who steals corporate secrets..."
} | ConvertTo-Json
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/" `
-Method POST `
-Body $body `
-ContentType "application/json"
# Output:
id : 2
title : Inception
year : 2010
genre : Sci-Fi
rating : 8.8
age : 14
description : A thief who steals...
# PowerShell:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/"
# Ausgabe: Array von Filmen
# Mit Filtering:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/?year=2010"
# Mit Search:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/?search=Matrix"
# PowerShell:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/"
# curl (Linux/Mac):
curl http://127.0.0.1:8000/api/movies/1/
# PowerShell:
$updateBody = @{
rating = "9.5"
} | ConvertTo-Json
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/" `
-Method PATCH `
-Body $updateBody `
-ContentType "application/json"
# PowerShell:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/" `
-Method DELETE
# curl:
curl -X DELETE http://127.0.0.1:8000/api/movies/1/
# Top-Rated Filme:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/top_rated/"
# Besetzung eines Films:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/movies/1/castings/"
# Filme eines Künstlers:
Invoke-RestMethod -Uri "http://127.0.0.1:8000/api/artists/1/movies/"
FirstMovieAPI/
├── firstmovieapi/
│ ├── settings.py ✏️ MODIFIZIERT
│ └── urls.py ✏️ MODIFIZIERT
├── movies/
│ ├── models.py ✅ VORHANDEN
│ ├── serializers.py ✨ NEU
│ ├── views.py ✨ NEU
│ ├── urls.py ✨ NEU
│ ├── filters.py ✨ NEU (optional)
│ └── admin.py ✏️ MODIFIZIERT (optional)
└── manage.py
movies/models.py (bereits vorhanden)movies/serializers.py (NEU)movies/views.py (NEU)movies/urls.py (NEU)firstmovieapi/settings.py (modifiziert)firstmovieapi/urls.py (modifiziert)# filepath: movies/models.py
from django.db import models
class Movie(models.Model):
"""Model für Filme"""
title = models.CharField(max_length=200, verbose_name="Titel")
year = models.IntegerField(verbose_name="Erscheinungsjahr")
genre = models.CharField(max_length=100, verbose_name="Genre", blank=True)
rating = models.DecimalField(
max_digits=3,
decimal_places=1,
null=True,
blank=True,
verbose_name="Bewertung"
)
description = models.TextField(verbose_name="Beschreibung", blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Film"
verbose_name_plural = "Filme"
ordering = ['-year', 'title']
def __str__(self):
return f"{self.title} ({self.year})"
class Artist(models.Model):
"""Model für Schauspieler/Künstler"""
first_name = models.CharField(max_length=100, verbose_name="Vorname")
last_name = models.CharField(max_length=100, verbose_name="Nachname")
birth_date = models.DateField(verbose_name="Geburtsdatum", null=True, blank=True)
nationality = models.CharField(max_length=100, verbose_name="Nationalität", blank=True)
biography = models.TextField(verbose_name="Biografie", blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Künstler"
verbose_name_plural = "Künstler"
ordering = ['last_name', 'first_name']
def __str__(self):
return f"{self.first_name} {self.last_name}"
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
class MovieCasting(models.Model):
"""Brückentabelle: Welcher Artist spielt in welchem Movie"""
movie = models.ForeignKey(
Movie,
on_delete=models.CASCADE,
related_name='castings',
verbose_name="Film"
)
artist = models.ForeignKey(
Artist,
on_delete=models.CASCADE,
related_name='movie_roles',
verbose_name="Künstler"
)
role_name = models.CharField(max_length=200, verbose_name="Rollenname")
is_main_role = models.BooleanField(default=False, verbose_name="Hauptrolle")
order = models.IntegerField(default=0, verbose_name="Reihenfolge")
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Besetzung"
verbose_name_plural = "Besetzungen"
ordering = ['order', 'artist__last_name']
unique_together = [['movie', 'artist', 'role_name']]
def __str__(self):
role_type = "Hauptrolle" if self.is_main_role else "Nebenrolle"
return f"{self.artist.full_name} als {self.role_name} in {self.movie.title} ({role_type})"
~500+ Zeilen Code
~150 Zeilen Code
JWT Tokens
pip install djangorestframework-simplejwt
# Token-basierte Auth
POST /api/token/
→ access & refresh tokens
Granulare Zugriffsrechte
from rest_framework.permissions import IsAuthenticated
class MovieViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
Swagger/OpenAPI
pip install drf-spectacular
# Automatische Swagger-UI
GET /api/docs/
API Tests schreiben
from rest_framework.test import APITestCase
class MovieAPITest(APITestCase):
def test_create_movie(self):
# ...
Production-Ready
Baue deine eigene API für: